
const cvs = document.querySelector('canvas');
const ctx = cvs.getContext('2d');
let user = document.getElementById('user');
let submit = document.getElementById('submit');
let statusArea = document.getElementById('status');
let ipBox = document.getElementById('ip');

let host = window.location.host;
if (host == '') {  // for file:// , host is empty
    host = '127.0.0.1:8080';
}
ipBox.value = host;
console.log('host=', host);


const GRID = {
    LAND: 0,
    STONE: 1,
    HILL: 2,
}
const GWIDTH = 100;
const GHEIGHT = 100;

let numberPlayer = 0;
let playerList = []; // all players
let numberArrow = 0;
let arrowList = [];  // all arrows
let col = 3;
let row = 3;
let color = ['red', 'orange', 'yellow', 'blue', 'cyan', 'purple', ];
let myPlayer = undefined;
let socket = undefined;

let board = [[0, 0, 0],
             [0, 1, 0],
             [0, 0, 2],];

// do NOT start with 0 (NORTH=1)
const DIR = {
    NORTH: 1,
    WEST: 2,
    SOUTH: 3,
    EAST: 4,
};


function gridX(x){
    let gx;
    gx = Math.floor(x / GWIDTH);
    return gx;
}


function gridY(y){
    let gy;
    gy = Math.floor(y / GHEIGHT);
    return gy;
}


function pixelX(gx) {
    let x = gx * GWIDTH;
    return x;
}
    
    
function pixelY(gy) {
    let y = gy * GHEIGHT;
    return y;
}
    

function gridValid(gx, gy){
    if(gy < 0 || gy >= row){
        return false;
    }
    if(gx < 0 || gx >= col){
        return false;
    }
    return true;
}


/**
 * 
 * @param {Number1} dir 
 * @returns {Number} diff x
 */
function dir2dx(dir)
{
    return (dir - 3) * (1 ^ (dir & 1));
}


/**
 * 
 * @param {Number} dir 
 * @returns {Number} diff y
 */
function dir2dy(dir)
{
    return (dir - 2) * (dir & 1);    
}


class Arrow {
    constructor() {
        // name of arrow always start with -
        this.name = '-x'; // arrow of x
        this.x = -1;
        this.y = -1;
        this.tx = -1;
        this.ty = -1;
        this.tcount = 0;
        this.tmax = 5;
        this.dir = DIR.NORTH; // this is calculated from x, y and tx, ty
    }

    init(name, x, y, dir) {
        this.name = name;
        this.x = x;
        this.y = y;
        this.dir = dir;
        this.tx = x;
        this.ty = y;
        this.tcount = 0;
        return this;
    }

    setPosFromString(str) {
        let list = str.split(':');
        if (list.length < 7) {
            console.log('ERROR Arrow.setPosFromString list=', list);
            return -2;
        }

        this.x = Number(list[0].trim());
        this.y = Number(list[1].trim());
        this.tx = Number(list[2].trim());
        this.ty = Number(list[3].trim());
        this.tcount = Number(list[4].trim());
        this.tmax = Number(list[5].trim());
        this.dir = Number((list[6].trim()));

        return 0;
    }

}
            
class Player {

    constructor() {
        this.name = 'x';
        this.x = -1;
        this.y = -1;
        this.tx = -1;
        this.ty = -1;
        this.tcount = -1;
        this.tmax = -1;
        this.dir = DIR.NORTH;
    }

    // TODO may remove this
    init(name, x, y) {
        this.name = name;
        this.x = x;
        this.y = y;
        this.tx = -1;
        this.ty = -1;
        this.tcount = -1;
        this.tmax = 10;
        return this;
    }

    /**
     * 
     * remember to trim
     * @param {String} str  x:y:tx:ty:tcount:tmax
     */
    setPosFromString(str) {
        let list = str.split(':');
        if (list.length < 7) {
            console.log('ERROR Player.setPosFromString list=', list);
            return -2;
        }

        this.x = Number(list[0].trim());
        this.y = Number(list[1].trim());
        this.tx = Number(list[2].trim());
        this.ty = Number(list[3].trim());
        this.tcount = Number(list[4].trim());
        this.tmax = Number(list[5].trim());
        this.dir = Number(list[6].trim());

        return 0;
    }
   
}


function arrowFromString(str) {
    let p = new Arrow();
    let list;
    list = str.split(':');
    p.name = list[0];
    if (false == p.name.startsWith('-')) {
        console.log('BUGBUG arrow name: ', p.name);
    }
    p.x = Number(list[1].trim());
    p.y = Number(list[2].trim());
    p.tx = Number(list[3].trim());
    p.ty = Number(list[4].trim());
    p.tcount = Number(list[5].trim());
    p.tmax = Number(list[6].trim());
    p.dir = Number(list[7].trim());

    return p;
}


/**
 * 
 * @param {String} name 
 */
function getArrow(name) {
    for (const p of arrowList) {
        // console.log('getArrow name=', name);
        if (p.name === name) {
            return p;
        }
    }
    return undefined;
}

/**
 * 
 * @param {Arrow} ta 
 */
function removeArrow(ta) {
    let name = ta.name;
    for (let i = 0; i < arrowList.length; i++) {
        let a = arrowList[i];
        if (name === a.name) {
            arrowList.splice(i, 1);
            return; // must return, remove ONLY 1
        }

    }
    console.log('WARN removeArrow not found: ', name);
    return ;
}


/**
 * 
 * @param {String} str, : separated
 * @return Player
 */
function playerFromString(str) {
    let p = new Player();
    let list;
    list = str.split(':');
    p.name = list[0];
    p.x = Number(list[1].trim());
    p.y = Number(list[2].trim());
    p.tx = Number(list[3].trim());
    p.ty = Number(list[4].trim());
    p.tcount = Number(list[5].trim());
    p.tmax = Number(list[6].trim());
    p.dir = Number(list[7].trim());

    return p;
}


/**
 * from name, get the player object
 * @param {String} name 
 * @returns {Player} player with correct name, undefined if not found
 */
function getPlayer(name) {
    for (const p of playerList) {
        // console.log('getPlayer name=', p.name);
        if (p.name === name) {
            return p;
        }
    }
    return undefined;
}


// draw a box, smaller than the grid (e.g. GWIDTH / 2 * GHEIGTH /2)
// write the name
// here: x, y are the gx, gy
function drawPlayer() {
    // for ... i = 0   i < playerList.length ...
    // playerList[i].name
    // playerList[i].x
    // playerList[i].y
    let x, y, name, cx, cy, radius;
    for(i = 0; i < playerList.length; i++){
        let player = playerList[i];
        if (player.x < 0 || player.y < 0) {
            // TODO flash the killed player before completely die
            continue; // skip killed player
        }
        x = GWIDTH * player.x;
        y = GHEIGHT * player.y;
        if (player.tx >= 0) {
            x += ((GWIDTH * (player.tx - player.x)) * player.tcount) / player.tmax;
            y += ((GHEIGHT * (player.ty - player.y)) * player.tcount) / player.tmax;
        }
        name = player.name;
        cx = x + GWIDTH/2;
        cy = y + GHEIGHT/3 + 3;
        radius = GHEIGHT/7;
        ctx.strokeStyle = color[i];
        ctx.fillStyle = color[i];
        ctx.beginPath();
        ctx.arc(cx, cy, radius, 0, 2*Math.PI, false);
        ctx.moveTo(x + GWIDTH/2, cy + radius);
        let ybase = y + 6*GHEIGHT / 7;  // 5/6 or 6/7 are ok
        ctx.lineTo(x + GWIDTH/4, ybase);
        ctx.lineTo(x + 3*GWIDTH/4, ybase);
        ctx.closePath();
        ctx.fill();
        if(name == user.value){
            ctx.fillStyle = 'silver';
            ctx.font = '24px courier';
        } else {
            ctx.fillStyle = 'black';
            ctx.font = '20px courier';
        }

        // center proportional font text
        // https://stackoverflow.com/questions/13771310/center-proportional-font-text-in-an-html5-canvas
        let textX = (GWIDTH - ctx.measureText(name).width) / 2;
        ctx.fillText(name, x + textX, y + GHEIGHT / 5 );

        // draw the eyes direction, TODO draw hands for different dir
        let eyeX = cx, eyeY = cy;  // only one eye
        let eyeOffset = 7;
        let eyeRadius = 4;
        ctx.fillStyle = 'black';  // draw black eye
        ctx.strokeStyle = 'black';
        eyeX += eyeOffset * dir2dx(player.dir);
        eyeY += eyeOffset * dir2dy(player.dir);
        // remove below if needed, keep for reference
        switch (0) {
            case DIR.NORTH:
                eyeY -= eyeOffset;
                break;
            case DIR.SOUTH:
                eyeY += eyeOffset;
                break;
            case DIR.WEST:
                eyeX -= eyeOffset;
                break;
            case DIR.EAST:
                eyeX += eyeOffset;
                break;
        }
        ctx.beginPath();
        ctx.arc(eyeX, eyeY, eyeRadius, 0, 2*Math.PI, false);
        ctx.closePath();
        ctx.stroke();
        ctx.fill();

    }
}


function drawArrow() {
    let x, y, cx, cy;
    let MARGIN = 5;
    let ARROW_HEAD = 8;
    for (const a of arrowList) {
        // top left corner
        x = pixelX(a.x);
        y = pixelY(a.y);
        cx = x + GWIDTH / 2;
        cy = y + GHEIGHT / 2;
        // TODO base on tcount, tmax,  change x, y
        if (a.tx >= 0) {
            x += GWIDTH * (a.tx - a.x) * a.tcount / a.tmax;
            y += GHEIGHT * (a.ty - a.y) * a.tcount / a.tmax;
        }
        // console.log('drawArrow : ', x, cy, a.dir);

        ctx.strokeStyle = 'silver';
        ctx.fillStyle = 'silver';
        switch (a.dir) {
            case DIR.WEST:
                ctx.beginPath();
                ctx.moveTo(x + MARGIN, cy);
                ctx.lineTo(x + MARGIN + ARROW_HEAD, cy - ARROW_HEAD);
                ctx.lineTo(x + MARGIN + ARROW_HEAD, cy + ARROW_HEAD);
                ctx.closePath();
                ctx.fillRect(x + MARGIN + ARROW_HEAD, cy, GWIDTH - MARGIN * 2 - ARROW_HEAD, 1);
                break;

                case DIR.EAST:
                    ctx.beginPath();
                    ctx.moveTo(x + GWIDTH - MARGIN, cy);
                    ctx.lineTo(x + GWIDTH - MARGIN - ARROW_HEAD, cy - ARROW_HEAD);
                    ctx.lineTo(x + GWIDTH - MARGIN - ARROW_HEAD, cy + ARROW_HEAD);
                    ctx.closePath();
                    ctx.fillRect(x + MARGIN, cy, GWIDTH - MARGIN * 2, 1);
                    break;

                case DIR.SOUTH:
                    ctx.beginPath();
                    ctx.moveTo(cx, y + GHEIGHT - MARGIN);
                    ctx.lineTo(cx - ARROW_HEAD, y + GHEIGHT - MARGIN - ARROW_HEAD);
                    ctx.lineTo(cx + ARROW_HEAD, y + GHEIGHT - MARGIN - ARROW_HEAD);
                    ctx.closePath();
                    ctx.fillRect(cx, y + MARGIN, 1, GHEIGHT - MARGIN * 2);
                    break;

                case DIR.NORTH:
                    ctx.beginPath();
                    ctx.moveTo(cx, y + MARGIN);
                    ctx.lineTo(cx - ARROW_HEAD, y + MARGIN + ARROW_HEAD);
                    ctx.lineTo(cx + ARROW_HEAD, y + MARGIN + ARROW_HEAD);
                    ctx.closePath();
                    ctx.fillRect(cx, y + MARGIN, 1, GHEIGHT - MARGIN * 2);
                    break;
                }
        ctx.stroke();
        ctx.fill();

    }

}


function drawBoard() {
    let x, y, x1, x2, y1, y2;
    let cx, cy, radius;

    ctx.clearRect(0, 0, cvs.clientWidth, cvs.clientHeight);

    ctx.beginPath();
    ctx.strokeStyle = 'brown';

    x1 = 0;
    x2 = col * GWIDTH;
    for (let r = 0; r<=row; r++){
        y = r * GHEIGHT;
        ctx.moveTo(x1, y);
        ctx.lineTo(x2, y);
    }
    y1 = 0;
    y2 = row * GHEIGHT;
    for (let c = 0; c<=col; c++){
        x = c* GWIDTH;
        ctx.moveTo(x, y1);
        ctx.lineTo(x, y2);
    }
    ctx.stroke();

    for(y = 0; y<row; y++){
        for(x = 0; x<col; x++){
            switch(board[y][x]){
                case GRID.LAND:
                    ctx.fillStyle = 'green';
                    ctx.fillRect(x*GWIDTH+1, y*GHEIGHT+1, GWIDTH-2, GHEIGHT-2);
                    break;
                case GRID.STONE:
                    ctx.strokeStyle = 'gray';
                    ctx.fillStyle = 'gray';
                    cx = x*GWIDTH + GWIDTH / 2;
                    cy = y*GHEIGHT + GHEIGHT / 2;
                    radius = Math.min(GWIDTH, GHEIGHT) / 2 - 4;
                    ctx.beginPath();
                    ctx.arc(cx, cy, radius, 0, 2*Math.PI, false);
                    ctx.closePath();
                    ctx.fill();
                    break;
                case GRID.HILL:
                    ctx.strokeStyle = 'brown';
                    ctx.fillStyle = 'brown';
                    ctx.beginPath();
                    ctx.moveTo(x*GWIDTH+GWIDTH/2, y*GHEIGHT+GHEIGHT/11);
                    ctx.lineTo(x*GWIDTH+GWIDTH/11, y*GHEIGHT+10*GHEIGHT/11);
                    ctx.lineTo(x*GWIDTH+10*GWIDTH/11,y*GHEIGHT+10*GHEIGHT/11);
                    ctx.closePath();
                    ctx.fill();
                    break;
            }
        }
    }
}


/**
 * 
 * @param {String} data 
 * @returns 
 */
function handleMoveOne(data) {
    // name x y
    let list = data.split(':');
    let x, y, name;
    // console.log('handleMoveOne data=', data);
    name = list[0];

    if (name.startsWith('-')) {
        let a = getArrow(name);
        if (undefined === a) {
            // console.log('ERROR handleMove getArrow undefined: ', name);
            a = new Arrow().init(name, -1, -1, -1);
            a = arrowFromString(data);
            arrowList[arrowList.length] = a;
            numberArrow = arrowList.length;
            return 0;
        }
        a.setPosFromString(data.substring(name.length + 1));
        if (-1 == a.x) {
            removeArrow(a);
        }
    } else {
        let p;
        p = getPlayer(name);
        if (p === undefined) {
            console.log('ERROR handleMove getPlayer undefined: ', name);
            return -1;
        }
        p.setPosFromString(data.substring(name.length + 1));
        if (p.name == myPlayer.name && p.x < 0) {
            statusArea.value = 'WARN you are dead !!';
        }
    }

}

function handleMove(data) {
    // console.log('handleMove data=', data);
    let lineList = data.split('|');
    // console.log('line[0]=' + lineList[0]);
    for (let i = 0; i < lineList.length; i++) {
        let line = lineList[i];
        if (line.length <= 1) {
             continue; // skip empty or very short line
        }
        // console.log('handleMove line=', line.length, line);
        handleMoveOne(line);
    }
    drawBoard();
    drawPlayer();
    drawArrow();
}


let receivedColRow = false;
let receivedRow = 0;
// ref
// https://stackoverflow.com/questions/12407778/connecting-to-tcp-socket-from-browser-using-javascript
function login(user) {
    // using standard websocket, proxy in 8080
    const ip = ipBox.value;
    console.log("login ip=" + ip);
    if (socket != undefined) {
        socket.close();
    }
    socket = new WebSocket("ws://" + ip + "/app/websocket/proxy");
    receivedColRow = false;
    receivedRow = 0;
    numberPlayer = 0;
    numberArrow = 0;
    playerList = [];
    arrowList = [];

    socket.addEventListener("open", (event) => {
        socket.send(user + "\n");
    });

    socket.addEventListener("message", (event) => {
        if (numberPlayer > 0 && playerList.length >= numberPlayer) {
            handleMove(event.data);
            return;
        }
        console.log("From server: ", event.data);
        // event.data : 
        // col row numPlayer
        // row 1
        // row 2
        // ...
        // split
        //     let list = st.split(' '); // split by space

        let list = event.data.split('|');
        
        for (let i=0; i<list.length; i++) {
            console.log('list: ' + list[i]);
        }

        // assume first line is alwasy col row
        if (receivedColRow == false) {
            let colRow = list[0].split(' ');
            if (colRow.length > 0 && 'ERROR' == colRow[0]) {
                console.log("error detected |" + colRow[0] + '|');
                statusArea.value = list[0];
                return; // early exit
            }
            col = Number(parseInt(colRow[0], 10));
            row = Number(parseInt(colRow[1], 10));
            numberPlayer = Number(parseInt(colRow[2], 10));
            numberArrow = Number(colRow[3].trim()); // no need parseInt
            console.log('col, row, numberPlayer, numberArrow=', col, row, numberPlayer, numberArrow);
            receivedColRow = true;
            list.splice(0, 1); // remove the first line
            board = []; // renew the board
        }

        if (receivedRow < row) {
            let addRow = 0;
            let i;
            for (i=0; i<list.length; i++) {
                let r = i + receivedRow;
                board[r] = [];
                let rowList = list[i].split(' ');
                // may change to: rowList.length <= 1 for newline check
                if (rowList.length < col) {
                    break;
                }
                // console.log('list[i]=', list[i]);
                // console.log('rowList = ', rowList);
                for (let c=0; c < col; c++) {
                    board[r][c] = Number(parseInt(rowList[c], 10)); 
                }
                addRow ++;
                if (addRow + receivedRow >= row) {
                    break;
                }
            }
            // delete list array start from receivedRow, remove total i row
            // in most case (assume one packet netork)
            // list.splice(0, row);  // this is the same
            list.splice(0, i + 1);
            receivedRow += addRow;
        }

        if (receivedRow >= row) {
            // receive player list here
            for (let i = 0; i < numberPlayer; i++) {
                // console.log("receive player: list[i] = ", list[i]);
                playerList[i] = playerFromString(list[i]);
            }
            list.splice(0, numberPlayer);

            for (let i = 0; i < numberArrow; i++) {
                // console.log("receive arrow: list[i] = ", list[i]);
                arrowList[i] = arrowFromString(list[i]);
            }
            list.splice(0, numberArrow); // may not need for last action
        }
        // avoid empty line
        // if (receivedRow >= row) {
        if (playerList.length >= numberPlayer && arrowList.length >= numberArrow) {
            console.log("FULL board received, draw col row=", col, row);
            console.log("board = ", board);
            for (let i = 0; i < playerList.length; i++) {
                console.log("[" + i + "]" + playerList[i].name + " in (" + playerList[i].x + ',' + playerList[i].y + ')');
                if(user == playerList[i].name){
                    myPlayer = playerList[i];
                }
            }
            statusArea.value = 'SUCCESS login, map, playerList';
            if (myPlayer.x < 0 || myPlayer.y < 0) {
                statusArea.value = 'WARN You are dead !!!';
            }
            drawBoard(); 
            drawPlayer();
            drawArrow();

        }

    });

}


/**
 * 
 * @param {KeyboardEvent} event 
 */
function onKey(event){
    let dir = -1;
    // console.log('onKey : ' + event.key);

    if (myPlayer == undefined) {
        console.log('ERROR onKey: myPlayer undefined !!');
        return;
    }
    // already dead
    if (myPlayer.x < 0) {
        console.log('WARN onKey you are dead');
        return;
    }


    // console.log('key: ' + event.key);
    if (event.key == 'w' || event.key == 'ArrowUp') {
        dir = DIR.NORTH;
    }
    if (event.key == 's' || event.key == 'ArrowDown') {
        dir = DIR.SOUTH;
    }
    if (event.key == 'a' || event.key == 'ArrowLeft') {
        dir = DIR.WEST;
    }
    if (event.key == 'd' || event.key == 'ArrowRight') {
        dir = DIR.EAST;
    }
    if (event.key == ' ') {
        dir = 0;
        console.log('onKey shoot arrow!!!');
    }

    // if (gridValid(tx, ty) == false) {
    //     console.log('WARN onKey invalid tx, ty: ', tx, ty);
    //     return;
    // }

    let msg = dir + '\n';
    console.log('onKey sending: ' + msg);
    socket.send(msg);
}


/**
 * 
 * @param {MouseEvent} event 
 */
function onClick(event){
    let x, y;
    let gx, gy, tx, ty;
    let valid = false;
    let dir;
    x = event.offsetX;
    y = event.offsetY;

    gx = gridX(x);
    gy = gridY(y);
    valid = gridValid(gx, gy);

    tx = gx - myPlayer.x;
    ty = gy - myPlayer.y;

    if(myPlayer == undefined){
        return;
    }
    // allow 1 or 0 (myself means shoot)
    if(Math.abs(tx) + Math.abs(ty) > 1) {
        return;
    }

    if(tx == 0 && ty == 0){
        dir = 0;
    }else{
        dir = 1 + (tx + 2)*Math.abs(tx) +(ty + 1)*Math.abs(ty);
    }

    // now we can MOVE here
    // TODO send : player.name x y
    // NOTE player name will automatically added by server
    let msg = dir + '\n';
    socket.send(msg);
    // console.log('MOVE: ', msg);
}


// also include keyboard event
function initMouseEvent() {
    // cvs event is ok, no need to have window event (same?)
    cvs.addEventListener("click", onClick);
    window.addEventListener("keydown", onKey, false);
}

// login(user.value);
drawBoard();
initMouseEvent();

function testPlayer() {
    let str = 'peter:5:6:-1:-2:-3:-4';
    let p;
    p = playerFromString(str);
    if (p.x !== 5) {
        console.log('testPlayer: bug1 p.x !== 5');
        return -1;
    }
    // console.log('playerFromString =', p);

    playerList = [];
    playerList[0] = new Player().init('hui', 0, 0);
    playerList[1] = new Player().init('peter', 3, 0);
    
    // console.log('playerList = ', playerList);

    p = getPlayer('hui');
    // console.log('getPlayer hui: ', p);
    if (p.x != 0) {
        console.log('testPlayer: get hui error: ' + p);
        return -2;
    }

    p = getPlayer('peter');
    // console.log('getPlayer peter: ', p);
    if (p.x != 3) {
        console.log('testPlayer: get peter error: ' + p);
        return -3;
    }


    // get hui, and set position
    p = getPlayer('hui');

    str = '0:1:2:3:4:10:3';
    p.setPosFromString(str);
//     console.log('testPlayer setPosFromString hui: ', p);
    if (p.x != 0 || p.y != 1 || p.tx != 2 || p.ty != 3 || p.tcount != 4 || p.tmax != 10) {
        console.log('testPlayer setPosFromString hui error: ', p);
        return -4;
    }
   
}

// testPlayer();


playerList = [];
playerList[0] = new Player().init('hui', 1, 0);
playerList[1] = new Player().init('peter', 1, 2);
playerList[1].dir = DIR.SOUTH;
playerList[2] = new Player().init('cindy', 2, 1);
playerList[2].dir = DIR.EAST;
playerList[3] = new Player().init('xiaoyu', 0, 1);
playerList[3].dir = DIR.WEST;
console.log('player[0]=', playerList[0]);
// let p = playerList[0];
// p.tx = 2;
// p.ty = 0;
// p.tcount = 1;
// p.tmax = 10;
arrowList = [];
arrowList[0] = new Arrow().init('-hui', 0, 0, DIR.WEST);
arrowList[1] = new Arrow().init('-hui', 2, 0, DIR.EAST);
arrowList[2] = new Arrow().init('-hui', 1, 1, DIR.SOUTH);
arrowList[3] = new Arrow().init('-hui', 0, 2, DIR.NORTH);
// arrow 2 to south, adjust tcount
arrowList[2].tx = 1 + dir2dx(arrowList[2].dir);
arrowList[2].ty = 1 + dir2dy(arrowList[2].dir);
arrowList[2].tcount = 2;

drawPlayer();
drawArrow();
console.log('user = ' + user.value);


